"""
认证服务
处理用户认证、JWT令牌生成和验证
"""

from datetime import datetime, timedelta
import json
from typing import Optional, Dict, Any, List
import uuid
import bcrypt
from jose import JWTError, jwt
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.orm import Session

from src.config.settings import settings
from src.config.database import get_db
from src.models.user import User, UserLogin, Token, TokenData, UserResponse, UserCreate, UserRole, UserStatus
from src.utils.logging import get_logger
from src.services.security_service import PasswordSecurity

logger = get_logger(__name__)


oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")


class AuthService:
    """认证服务类"""
    
    # 存储已撤销的令牌（在生产环境中应使用Redis等外部存储）
    revoked_tokens = set()
    
    @staticmethod
    def is_token_revoked(token: str) -> bool:
        """检查令牌是否已被撤销"""
        return token in AuthService.revoked_tokens
    
    @staticmethod
    def revoke_token(token: str) -> None:
        """撤销令牌"""
        AuthService.revoked_tokens.add(token)
    
    @staticmethod
    def hash_password(password: str) -> str:
        """哈希密码"""
        salt = bcrypt.gensalt()
        hashed_password = bcrypt.hashpw(password.encode('utf-8'), salt)
        logger.debug("密码哈希处理完成")
        return hashed_password.decode('utf-8')
    
    @staticmethod
    def verify_password(plain_password: str, hashed_password: str) -> bool:
        """验证密码"""
        return bcrypt.checkpw(
            plain_password.encode('utf-8'), 
            hashed_password.encode('utf-8')
        )
    
    @staticmethod
    def check_password_history(db: Session, user: User, new_password: str) -> bool:
        """检查密码历史，防止重复使用旧密码"""
        # 获取字段的实际值而不是Column对象
        password_history_value = getattr(user, 'password_history', None)
        if not password_history_value:
            return True
            
        try:
            # 解析密码历史记录
            password_history = json.loads(password_history_value)
        except (json.JSONDecodeError, TypeError):
            # 如果解析失败，只检查当前密码
            password_hash_value = getattr(user, 'password_hash', "")
            return not AuthService.verify_password(new_password, password_hash_value)
        
        # 检查新密码是否与历史密码匹配
        for old_hash in password_history:
            if AuthService.verify_password(new_password, old_hash):
                return False
        
        return True
    
    @staticmethod
    def add_password_to_history(db: Session, user: User, new_password_hash: str) -> None:
        """将密码添加到密码历史记录中"""
        # 获取字段的实际值而不是Column对象
        password_history_value = getattr(user, 'password_history', None)
        try:
            # 解析现有密码历史记录
            if password_history_value:
                password_history = json.loads(password_history_value)
            else:
                password_history = []
        except (json.JSONDecodeError, TypeError):
            password_history = []
        
        # 添加当前密码到历史记录
        password_hash_value = getattr(user, 'password_hash', "")
        password_history.append(password_hash_value)
        
        # 保留最近5个密码历史
        if len(password_history) > 5:
            password_history = password_history[-5:]
        
        # 更新用户记录
        user.password_history = json.dumps(password_history)  # type: ignore
        db.commit()
    
    @staticmethod
    def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
        """创建JWT访问令牌"""
        to_encode: Dict[str, Any] = data.copy()
        if expires_delta:
            expire = datetime.utcnow() + expires_delta
        else:
            expire = datetime.utcnow() + timedelta(
                minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES
            )
        to_encode.update({"exp": expire})
        encoded_jwt: str = jwt.encode(
            to_encode, 
            settings.JWT_SECRET_KEY, 
            algorithm=settings.JWT_ALGORITHM
        )
        return encoded_jwt

    @staticmethod
    def get_user_by_username(db: Session, username: str) -> Optional[User]:
        """通过用户名获取用户"""
        return db.query(User).filter(User.username == username).first()

    @staticmethod
    def get_user_by_email(db: Session, email: str) -> Optional[User]:
        """通过邮箱获取用户"""
        return db.query(User).filter(User.email == email).first()

    @staticmethod
    def create_user(db: Session, user_data: UserCreate) -> User:
        """创建新用户"""
        # 检查密码强度
        password_check = PasswordSecurity.validate_password_strength(user_data.password)
        if not password_check["is_strong"]:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"密码强度不足: {'; '.join(password_check['issues'])}",
            )
        
        hashed_password = AuthService.hash_password(user_data.password)
        
        user = User(
            username=user_data.username,
            email=user_data.email,
            password_hash=hashed_password,
            role=user_data.role or "user",
            status="active"
        )
        
        db.add(user)
        db.commit()
        db.refresh(user)
        
        logger.info(f"用户创建成功: {user.username} (ID: {user.id})")
        return user

    @staticmethod
    def get_users(
        db: Session, 
        skip: int = 0, 
        limit: int = 100,
        role: Optional[str] = None,
        status: Optional[str] = None
    ) -> List[User]:
        """获取用户列表"""
        query = db.query(User)
        
        if role:
            query = query.filter(User.role == role)
        if status:
            query = query.filter(User.status == status)
            
        return query.offset(skip).limit(limit).all()

    @staticmethod
    def get_user_by_id(db: Session, user_id: uuid.UUID) -> Optional[User]:
        """通过ID获取用户"""
        return db.query(User).filter(User.id == user_id).first()

    @staticmethod
    def update_user_role(db: Session, user: User, new_role: str) -> User:
        """更新用户角色"""
        user.role = new_role  # type: ignore
        user.updated_at = datetime.utcnow()  # type: ignore
        db.commit()
        db.refresh(user)
        
        logger.info(f"用户角色更新成功: {user.username} -> {new_role}")
        return user

    @staticmethod
    def update_user_status(db: Session, user: User, new_status: str) -> User:
        """更新用户状态"""
        user.status = new_status  # type: ignore
        user.updated_at = datetime.utcnow()  # type: ignore
        db.commit()
        db.refresh(user)
        
        logger.info(f"用户状态更新成功: {user.username} -> {new_status}")
        return user

    @staticmethod
    def update_password(db: Session, user: User, new_password: str) -> User:
        """更新用户密码"""
        # 检查密码强度
        password_check = PasswordSecurity.validate_password_strength(new_password)
        if not password_check["is_strong"]:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"密码强度不足: {'; '.join(password_check['issues'])}",
            )
        
        # 检查密码历史
        if not AuthService.check_password_history(db, user, new_password):
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="新密码不能与最近使用的密码相同",
            )
        
        # 添加当前密码到历史记录
        password_hash_value = getattr(user, 'password_hash', "")
        AuthService.add_password_to_history(db, user, password_hash_value)
        
        # 更新密码
        user.password_hash = AuthService.hash_password(new_password)  # type: ignore
        user.updated_at = datetime.utcnow()  # type: ignore
        db.commit()
        db.refresh(user)
        
        logger.info(f"用户密码更新成功: {user.username}")
        return user
    
    @staticmethod
    def verify_token(token: str) -> TokenData:
        """验证JWT令牌"""
        try:
            payload = jwt.decode(
                token, 
                settings.JWT_SECRET_KEY, 
                algorithms=[settings.JWT_ALGORITHM]
            )
            user_id: Optional[str] = payload.get("sub")
            username: Optional[str] = payload.get("username")
            role: Optional[str] = payload.get("role")
            exp: Optional[int] = payload.get("exp")
            
            if user_id is None or username is None:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="无效的令牌",
                    headers={"WWW-Authenticate": "Bearer"},
                )
            
            # 尝试将user_id转换为UUID
            try:
                user_uuid = uuid.UUID(user_id)
            except ValueError:
                user_uuid = None
                
            return TokenData(
                user_id=user_uuid,
                username=username or "",
                role=UserRole(role) if role else UserRole.USER,  # type: ignore
                exp=datetime.fromtimestamp(exp) if exp else None
            )
        except JWTError:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="令牌验证失败",
                headers={"WWW-Authenticate": "Bearer"},
            )
    
    @staticmethod
    def authenticate_user(db: Session, username: str, password: str) -> Optional[User]:
        """用户认证"""
        # 通过用户名或邮箱查找用户
        user = db.query(User).filter(
            (User.username == username) | (User.email == username)
        ).first()
        
        if not user:
            return None
        
        password_hash_value = getattr(user, 'password_hash', "")
        if not AuthService.verify_password(password, password_hash_value):
            return None
        
        status_value = getattr(user, 'status', "")
        if status_value != "active":
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="用户账户已被暂停或禁用",
            )
        
        # 更新最后登录时间
        user.last_login_at = datetime.utcnow()  # type: ignore
        db.commit()
        
        return user
    
    @staticmethod
    def login_user(db: Session, login_data: UserLogin) -> Token:
        """用户登录"""
        user = AuthService.authenticate_user(
            db, login_data.username, login_data.password
        )
        
        if not user:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="用户名或密码错误",
                headers={"WWW-Authenticate": "Bearer"},
            )
        
        # 创建访问令牌
        access_token_expires = timedelta(
            minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES
        )
        access_token = AuthService.create_access_token(
            data={
                "sub": str(user.id),
                "username": user.username,
                "role": str(user.role)
            },
            expires_delta=access_token_expires
        )
        
        # 创建刷新令牌
        refresh_token_expires = timedelta(days=7)
        refresh_token = AuthService.create_refresh_token(
            data={
                "sub": str(user.id),
                "username": user.username,
                "role": str(user.role)
            },
            expires_delta=refresh_token_expires
        )
        
        logger.info(f"用户 {user.username} 登录成功")
        
        return Token(
            access_token=access_token,
            refresh_token=refresh_token,
            token_type="bearer",
            expires_in=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES * 60
        )
    
    @staticmethod
    def get_current_user(
        db: Session = Depends(get_db), 
        token: str = Depends(oauth2_scheme)
    ) -> User:
        """获取当前用户"""
        credentials_exception = HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="无法验证凭据",
            headers={"WWW-Authenticate": "Bearer"},
        )
        try:
            # 检查令牌是否已被撤销
            if AuthService.is_token_revoked(token):
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="令牌已被撤销",
                    headers={"WWW-Authenticate": "Bearer"},
                )
            
            payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[settings.JWT_ALGORITHM])
            user_id: str = payload.get("sub", "")  # type: ignore
            if user_id is None:
                raise credentials_exception
            # 尝试将user_id解析为UUID
            try:
                user_uuid = uuid.UUID(user_id)
                user = db.query(User).filter(User.id == user_uuid).first()
            except ValueError:
                # 如果user_id不是UUID，则假定它是用户名
                user = db.query(User).filter(User.username == user_id).first()
                
            if user is None:
                raise credentials_exception
            return user
        except JWTError:
            raise credentials_exception

    @staticmethod
    def get_current_active_user(
        db: Session = Depends(get_db),
        token: str = Depends(oauth2_scheme)
    ) -> User:
        """获取当前活跃用户"""
        current_user = AuthService.get_current_user(db, token)
        status_value = getattr(current_user, 'status', "")
        if status_value != "active":
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="用户账户不活跃",
            )
        return current_user
    
    @staticmethod
    def require_role(required_role: str):
        """角色权限验证装饰器"""
        def role_checker(current_user: User = Depends(AuthService.get_current_user)) -> User:
            # 检查用户角色是否匹配所需角色
            # 支持直接比较枚举值或字符串值
            user_role = current_user.role
            if isinstance(user_role, str):
                # 如果是字符串，转换为小写进行比较
                if user_role.lower() != required_role.lower():
                    raise HTTPException(
                        status_code=status.HTTP_403_FORBIDDEN,
                        detail="权限不足",
                    )
            else:
                # 如果是枚举，比较枚举值
                if str(user_role.value).lower() != required_role.lower():
                    raise HTTPException(
                        status_code=status.HTTP_403_FORBIDDEN,
                        detail="权限不足",
                    )
            return current_user
        return role_checker
    
    @staticmethod
    def require_any_role(required_roles: list):
        """任意角色权限验证装饰器"""
        def role_checker(current_user: User = Depends(AuthService.get_current_user)) -> User:
            # 检查用户角色是否在允许的角色列表中
            user_role = current_user.role
            if isinstance(user_role, str):
                # 如果是字符串，转换为小写进行比较
                user_role_lower = user_role.lower()
                if user_role_lower not in [role.lower() for role in required_roles]:
                    raise HTTPException(
                        status_code=status.HTTP_403_FORBIDDEN,
                        detail="权限不足",
                    )
            else:
                # 如果是枚举，比较枚举值
                user_role_value_lower = str(user_role.value).lower()
                if user_role_value_lower not in [role.lower() for role in required_roles]:
                    raise HTTPException(
                        status_code=status.HTTP_403_FORBIDDEN,
                        detail="权限不足",
                    )
            return current_user
        return role_checker
    
    @staticmethod
    def create_refresh_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
        """创建JWT刷新令牌"""
        to_encode: Dict[str, Any] = data.copy()
        if expires_delta:
            expire = datetime.utcnow() + expires_delta
        else:
            # 默认刷新令牌有效期为7天
            expire = datetime.utcnow() + timedelta(days=7)
        to_encode.update({"exp": expire, "type": "refresh"})
        encoded_jwt: str = jwt.encode(
            to_encode, 
            settings.JWT_SECRET_KEY, 
            algorithm=settings.JWT_ALGORITHM
        )
        return encoded_jwt
    
    @staticmethod
    def verify_refresh_token(token: str) -> TokenData:
        """验证JWT刷新令牌"""
        try:
            # 检查令牌是否已被撤销
            if AuthService.is_token_revoked(token):
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="令牌已被撤销",
                    headers={"WWW-Authenticate": "Bearer"},
                )
            
            payload = jwt.decode(
                token, 
                settings.JWT_SECRET_KEY, 
                algorithms=[settings.JWT_ALGORITHM]
            )
            
            # 检查令牌类型
            token_type = payload.get("type")
            if token_type != "refresh":
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="无效的令牌类型",
                    headers={"WWW-Authenticate": "Bearer"},
                )
            
            user_id: Optional[str] = payload.get("sub")
            username: Optional[str] = payload.get("username")
            role: Optional[str] = payload.get("role")
            exp: Optional[int] = payload.get("exp")
            
            if user_id is None or username is None:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="无效的令牌",
                    headers={"WWW-Authenticate": "Bearer"},
                )
            
            # 尝试将user_id转换为UUID
            try:
                user_uuid = uuid.UUID(user_id)
            except ValueError:
                user_uuid = None
                
            return TokenData(
                user_id=user_uuid,
                username=username or "",
                role=UserRole(role) if role else UserRole.USER,  # type: ignore
                exp=datetime.fromtimestamp(exp) if exp else None
            )
        except JWTError:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="令牌验证失败",
                headers={"WWW-Authenticate": "Bearer"},
            )
    
    @staticmethod
    def refresh_access_token(refresh_token: str) -> Token:
        """使用刷新令牌获取新的访问令牌"""
        # 验证刷新令牌
        token_data = AuthService.verify_refresh_token(refresh_token)
        
        # 创建新的访问令牌
        access_token_expires = timedelta(
            minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES
        )
        access_token = AuthService.create_access_token(
            data={
                "sub": str(token_data.user_id),
                "username": token_data.username,
                "role": token_data.role
            },
            expires_delta=access_token_expires
        )
        
        return Token(
            access_token=access_token,
            token_type="bearer",
            expires_in=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES * 60
        )

# 快捷依赖项
get_current_user_dep = AuthService.get_current_user
get_current_active_user_dep = AuthService.get_current_active_user
get_current_active_user = AuthService.get_current_user
require_admin = AuthService.require_role("admin")
require_agent_or_admin = AuthService.require_any_role(["agent", "admin"])


# 全局认证服务实例
auth_service = AuthService()


def get_user_info(user_id: str) -> dict:
    """获取用户信息 - 用于服务网关"""
    from src.config.database import get_db
    
    try:
        db = next(get_db())
        
        # 首先尝试通过用户名查找
        user = AuthService.get_user_by_username(db, user_id)
        
        if not user:
            # 如果不是用户名，尝试作为UUID查找
            try:
                user_uuid = uuid.UUID(user_id)
                user = AuthService.get_user_by_id(db, user_uuid)
            except ValueError:
                # 如果不是有效的UUID，继续尝试其他方式
                user = None
        
        if user:
            created_at_value = getattr(user, 'created_at', None)
            last_login_at_value = getattr(user, 'last_login_at', None)
            return {
                "id": str(user.id),
                "username": user.username,
                "email": user.email,
                "role": user.role,
                "status": user.status,
                "created_at": created_at_value.isoformat() if created_at_value else None,  # type: ignore
                "last_login_at": last_login_at_value.isoformat() if last_login_at_value else None  # type: ignore
            }
        else:
            return {"error": f"用户 '{user_id}' 未找到"}
            
    except Exception as e:
        return {"error": f"获取用户信息失败: {str(e)}"}


# 导出
__all__ = [
    "AuthService", 
    "auth_service",
    "get_user_info",
    "get_current_user_dep", 
    "get_current_active_user_dep",
    "get_current_active_user", 
    "require_admin", 
    "require_agent_or_admin"
]
